#include "U2Texture.h"

#include "U2LogManager.h"
#include "U2Image.h"
#include "U2Exception.h"
#include "U2DataStreamMemory.h"
#include "U2ResourceManager.h"
#include "U2ResourceGroupManager.h"
#include "U2TextureManager.h"
#include "U2HardwarePixelBuffer.h"


U2EG_NAMESPACE_USING


//--------------------------------------------------------------------------
U2Texture::U2Texture(U2ResourceManager* creator, const U2String& name, 
    U2ResourceHandle handle, const U2String& group, bool isManual, 
    U2ManualResourceLoader* loader)
: U2Resource(creator, name, handle, group, isManual, loader)
 // init defaults; can be overridden before load()
, mHeight(512)
, mWidth(512)
, mDepth(1)
, mNumRequestedMipmaps(0)
, mNumMipmaps(0)
, mMipmapsHardwareGenerated(false)
, mGamma(1.0f)
, mHwGamma(false)
, mFSAA(0)
, mTextureType(TEX_TYPE_2D)
, mFormat(PF_UNKNOWN)
, mUsage(TU_DEFAULT)
, mSrcFormat(PF_UNKNOWN)
, mSrcWidth(0)
, mSrcHeight(0)
, mSrcDepth(0)
, mDesiredFormat(PF_UNKNOWN)
, mDesiredIntegerBitDepth(0)
, mDesiredFloatBitDepth(0)
, mTreatLuminanceAsAlpha(false)
, mInternalResourcesCreated(false)
{
    if (createParamDictionary("Texture"))
    {
        // Define the parameters that have to be present to load
        // from a generic source; actually there are none, since when
        // predeclaring, you use a texture file which includes all the
        // information required.
    }

	// Set some defaults for default load path
	if (U2TextureManager::getSingletonPtr())
	{
		U2TextureManager& tmgr = U2TextureManager::getSingleton();
		setNumMipmaps(tmgr.getDefaultNumMipmaps());
		setDesiredBitDepths(tmgr.getPreferredIntegerBitDepth(), tmgr.getPreferredFloatBitDepth());
	}

    
}
//--------------------------------------------------------------------------
void U2Texture::loadRawData( U2DataStreamPtr& stream, 
	u2ushort uWidth, u2ushort uHeight, PixelFormat eFormat)
{
	U2Image img;
	img.loadRawData(stream, uWidth, uHeight, eFormat);
	loadImage(img);
}
//--------------------------------------------------------------------------    
void U2Texture::loadImage( const U2Image &img )
{

    LoadingState old = mLoadingState.get();
    if (old!=LOADSTATE_UNLOADED && old!=LOADSTATE_PREPARED) return;

    if (!mLoadingState.cas(old,LOADSTATE_LOADING)) return;

	// Scope lock for actual loading
	try
	{
		U2_LOCK_AUTO_MUTEX
        std::vector<const U2Image*> imagePtrs;
		imagePtrs.push_back(&img);
		_loadImages( imagePtrs );

	}
	catch (...)
	{
		// Reset loading in-progress flag in case failed for some reason
		mLoadingState.set(old);
		// Re-throw
		throw;
	}

    mLoadingState.set(LOADSTATE_LOADED);

	// Notify manager
	if(mCreator)
		mCreator->_notifyResourceLoaded(this);

	// No deferred loading events since this method is not called in background
}
//--------------------------------------------------------------------------
void U2Texture::setFormat(PixelFormat pf)
{
    mFormat = pf;
    mDesiredFormat = pf;
    mSrcFormat = pf;
}
//--------------------------------------------------------------------------
bool U2Texture::hasAlpha(void) const
{
    return U2PixelUtil::hasAlpha(mFormat);
}
//--------------------------------------------------------------------------
void U2Texture::setDesiredIntegerBitDepth(u2ushort bits)
{
    mDesiredIntegerBitDepth = bits;
}
//--------------------------------------------------------------------------
u2ushort U2Texture::getDesiredIntegerBitDepth(void) const
{
    return mDesiredIntegerBitDepth;
}
//--------------------------------------------------------------------------
void U2Texture::setDesiredFloatBitDepth(u2ushort bits)
{
    mDesiredFloatBitDepth = bits;
}
//--------------------------------------------------------------------------
u2ushort U2Texture::getDesiredFloatBitDepth(void) const
{
    return mDesiredFloatBitDepth;
}
//--------------------------------------------------------------------------
void U2Texture::setDesiredBitDepths(u2ushort integerBits, u2ushort floatBits)
{
    mDesiredIntegerBitDepth = integerBits;
    mDesiredFloatBitDepth = floatBits;
}
//--------------------------------------------------------------------------
void U2Texture::setTreatLuminanceAsAlpha(bool asAlpha)
{
    mTreatLuminanceAsAlpha = asAlpha;
}
//--------------------------------------------------------------------------
bool U2Texture::getTreatLuminanceAsAlpha(void) const
{
    return mTreatLuminanceAsAlpha;
}
//--------------------------------------------------------------------------
size_t U2Texture::calculateSize(void) const
{
    return getNumFaces() * U2PixelUtil::getMemorySize(mWidth, mHeight, mDepth, mFormat);
}
//--------------------------------------------------------------------------
size_t U2Texture::getNumFaces(void) const
{
	return getTextureType() == TEX_TYPE_CUBE_MAP ? 6 : 1;
}
//--------------------------------------------------------------------------
void U2Texture::_loadImages( const ConstImagePtrList& images )
{
	if(images.size() < 1)
		U2_EXCEPT(U2Exception::ERR_INVALIDPARAMS, "Cannot load empty vector of images",
		 "Texture::loadImages");
    
	// Set desired texture size and properties from images[0]
	mSrcWidth = mWidth = images[0]->getWidth();
	mSrcHeight = mHeight = images[0]->getHeight();
	mSrcDepth = mDepth = images[0]->getDepth();

    // Get source image format and adjust if required
    mSrcFormat = images[0]->getFormat();
    if (mTreatLuminanceAsAlpha && mSrcFormat == PF_L8)
    {
        mSrcFormat = PF_A8;
    }

    if (mDesiredFormat != PF_UNKNOWN)
    {
        // If have desired format, use it
        mFormat = mDesiredFormat;
    }
    else
    {
        // Get the format according with desired bit depth
        mFormat = U2PixelUtil::getFormatForBitDepths(mSrcFormat, mDesiredIntegerBitDepth, mDesiredFloatBitDepth);
    }

	// The custom mipmaps in the image have priority over everything
    size_t imageMips = images[0]->getNumMipmaps();

	if(imageMips > 0)
	{
		mNumMipmaps = mNumRequestedMipmaps = images[0]->getNumMipmaps();
		// Disable flag for auto mip generation
		mUsage &= ~TU_AUTOMIPMAP;
	}

    // Create the texture
    createInternalResources();
	// Check if we're loading one image with multiple faces
	// or a vector of images representing the faces
	size_t faces;
	bool multiImage; // Load from multiple images?
	if(images.size() > 1)
	{
		faces = images.size();
		multiImage = true;
	}
	else
	{
		faces = images[0]->getNumFaces();
		multiImage = false;
	}
	
	// Check wether number of faces in images exceeds number of faces
	// in this texture. If so, clamp it.
	if(faces > getNumFaces())
		faces = getNumFaces();
	
    if (U2TextureManager::getSingleton().getVerbose()) {
        // Say what we're doing
        U2StringUtil::U2StrStreamType str;
        str << "Texture: " << mName << ": Loading " << faces << " faces"
            << "(" << U2PixelUtil::getFormatName(images[0]->getFormat()) << "," <<
            images[0]->getWidth() << "x" << images[0]->getHeight() << "x" << images[0]->getDepth() <<
            ") with ";
        if (!(mMipmapsHardwareGenerated && mNumMipmaps == 0))
            str << mNumMipmaps;
        if(mUsage & TU_AUTOMIPMAP)
        {
            if (mMipmapsHardwareGenerated)
                str << " hardware";

            str << " generated mipmaps";
        }
        else
        {
            str << " custom mipmaps";
        }
        if(multiImage)
            str << " from multiple Images.";
        else
            str << " from U2Image.";
        // Scoped
        {
            // Print data about first destination surface
            U2HardwarePixelBufferSharedPtr buf = getBuffer(0, 0); 
            str << " Internal format is " << U2PixelUtil::getFormatName(buf->getFormat()) << 
            "," << buf->getWidth() << "x" << buf->getHeight() << "x" << buf->getDepth() << ".";
        }
        U2LogManager::getSingleton().logMessage( 
                LML_NORMAL, str.str());
    }
	
	// Main loading loop
    // imageMips == 0 if the image has no custom mipmaps, otherwise contains the number of custom mips
    for(size_t mip = 0; mip <= imageMips; ++mip)
    {
        for(size_t i = 0; i < faces; ++i)
        {
            U2PixelBox src;
            if(multiImage)
            {
                // Load from multiple images
                src = images[i]->getPixelBox(0, mip);
            }
            else
            {
                // Load from faces of images[0]
                src = images[0]->getPixelBox(i, mip);
            }

            // Sets to treated format in case is difference
            src.format = mSrcFormat;

            if(mGamma != 1.0f) {
                // Apply gamma correction
                // Do not overwrite original image but do gamma correction in temporary buffer
                U2DataStreamMemoryPtr buf; // for scoped deletion of conversion buffer
                buf.bind(U2_NEW U2DataStreamMemory(
                    U2PixelUtil::getMemorySize(
                        src.getWidth(), src.getHeight(), src.getDepth(), src.format)));
                
                U2PixelBox corrected = U2PixelBox(src.getWidth(), src.getHeight(), src.getDepth(), src.format, buf->getPtr());
                U2PixelUtil::bulkPixelConversion(src, corrected);
                
                U2Image::applyGamma(static_cast<u2uint8*>(corrected.data), mGamma, corrected.getConsecutiveSize(), 
                    static_cast<u2uchar>(U2PixelUtil::getNumElemBits(src.format)));

                // Destination: entire texture. blitFromMemory does the scaling to
                // a power of two for us when needed
                getBuffer(i, mip)->blitFromMemory(corrected);
            }
            else 
            {
                // Destination: entire texture. blitFromMemory does the scaling to
                // a power of two for us when needed
                getBuffer(i, mip)->blitFromMemory(src);
            }
            
        }
    }
    // Update size (the final size, not including temp space)
    mSize = getNumFaces() * U2PixelUtil::getMemorySize(mWidth, mHeight, mDepth, mFormat);

}
//-----------------------------------------------------------------------------
void U2Texture::createInternalResources(void)
{
	if (!mInternalResourcesCreated)
	{
		createInternalResourcesImpl();
		mInternalResourcesCreated = true;
	}
}
//-----------------------------------------------------------------------------
void U2Texture::freeInternalResources(void)
{
	if (mInternalResourcesCreated)
	{
		freeInternalResourcesImpl();
		mInternalResourcesCreated = false;
	}
}
//-----------------------------------------------------------------------------
void U2Texture::unloadImpl(void)
{
	freeInternalResources();
}
//-----------------------------------------------------------------------------   
void U2Texture::copyToTexture( U2TexturePtr& target )
{
    if(target->getNumFaces() != getNumFaces())
    {
        U2_EXCEPT(U2Exception::ERR_INVALIDPARAMS, 
            "Texture types must match",
            "Texture::copyToTexture");
    }
    size_t numMips = std::min(getNumMipmaps(), target->getNumMipmaps());
    if((mUsage & TU_AUTOMIPMAP) || (target->getUsage()&TU_AUTOMIPMAP))
        numMips = 0;
    for(unsigned int face=0; face<getNumFaces(); face++)
    {
        for(unsigned int mip=0; mip<=numMips; mip++)
        {
            target->getBuffer(face, mip)->blit(getBuffer(face, mip));
        }
    }
}
//---------------------------------------------------------------------
U2String U2Texture::getSourceFileType() const
{
	if (mName.empty())
		return U2StringUtil::BLANK;

	U2String::size_type pos = mName.find_last_of(".");
	if (pos != U2String::npos && pos < (mName.length() - 1))
	{
		U2String ext = mName.substr(pos+1);
		U2StringUtil::toLowerCase(ext);
		return ext;
	}
	else
	{
		// No extension
		U2DataStreamPtr dstream;
		try
		{
			dstream = U2ResourceGroupManager::getSingleton().openResource(
					mName, mGroup, true, 0);
		}
		catch (U2Exception&)
		{
		}
		if (dstream.isNull() && getTextureType() == TEX_TYPE_CUBE_MAP)
		{
			// try again with one of the faces (non-dds)
			try
			{
				dstream = U2ResourceGroupManager::getSingleton().openResource(
					mName + "_rt", mGroup, true, 0);
			}
			catch (U2Exception&)
			{
			}
		}

		if (!dstream.isNull())
		{
			return U2Image::getFileExtFromMagic(dstream);
		}
	}

	return U2StringUtil::BLANK;

}
//---------------------------------------------------------------------
void U2Texture::convertToImage(U2Image& destImage, bool includeMipMaps)
{

	size_t numMips = includeMipMaps? getNumMipmaps() + 1 : 1;
	size_t dataSize = U2Image::calculateSize(numMips,
		getNumFaces(), getWidth(), getHeight(), getDepth(), getFormat());

	void* pixData = U2_MALLOC(dataSize, MEMCATEGORY_GENERAL);
	// if there are multiple faces and mipmaps we must pack them into the data
	// faces, then mips
	void* currentPixData = pixData;
	for (size_t face = 0; face < getNumFaces(); ++face)
	{
		for (size_t mip = 0; mip < numMips; ++mip)
		{
			size_t mipDataSize = U2PixelUtil::getMemorySize(getWidth(), getHeight(), getDepth(), getFormat());

			U2PixelBox pixBox(getWidth(), getHeight(), getDepth(), getFormat(), currentPixData);
			getBuffer(face, mip)->blitToMemory(pixBox);

			currentPixData = (void*)((u2char*)currentPixData + mipDataSize);

		}
	}


	// load, and tell U2Image to delete the memory when it's done.
	destImage.loadDynamicImage((u2uchar*)pixData, getWidth(), getHeight(), getDepth(), getFormat(), true, 
		getNumFaces(), numMips - 1);

}


